﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Security.Cryptography;
using System.Collections.Specialized;
using System.Web;


namespace suanpan.API.Components.OAuth
{
    public class OAuthDigitalSigner
    {

        #region Properties
        //public string ConsumerKey { get; set; }
        public string ConsumerSecret { get; set; }
        public string CanonicalString { get; set; }
        public string DigitalSignature { get; set; }
        public DateTime? SignatureDateTime { get; set; }
        public string RequestUrl { get; set; }
        public string RequestMethod { get; set; }
        public NameValueCollection RequestParameters { get; set; }
        public string TokenSecret { get; set; }

        private string _consumerKey = string.Empty;
        public string ConsumerKey
        {
            get { return _consumerKey; }
            set
            {
                _consumerKey = value;
                if (null == this.RequestParameters[Parameters.OAuth_Consumer_Key])
                    this.RequestParameters.Add(Parameters.OAuth_Consumer_Key, value);
                else
                    this.RequestParameters[Parameters.OAuth_Consumer_Key] = value;
            }
        }

        private string _token = string.Empty;
        public string Token
        {
            get { return _token; }
            set
            {
                _token = value;
                if (null == this.RequestParameters[Parameters.OAuth_Token])
                    this.RequestParameters.Add(Parameters.OAuth_Token, value);
                else
                    this.RequestParameters[Parameters.OAuth_Token] = value;
            }
        }

        private SignatureMethod _signingMethod;
        public SignatureMethod SigningMethod
        {
            get { return _signingMethod; }
            set
            {
                _signingMethod = value;

                if (null == this.RequestParameters[Parameters.OAuth_Signature_Method])
                    this.RequestParameters.Add(Parameters.OAuth_Signature_Method, GetSignatureMethodName(value));
                else
                    this.RequestParameters[Parameters.OAuth_Signature_Method] = GetSignatureMethodName(value);

            }
        }

        private string _nonce = string.Empty;
        public string Nonce
        {
            get { return _nonce; }
            set
            {
                _nonce = value;

                if (null == this.RequestParameters[Parameters.OAuth_Nonce])
                    this.RequestParameters.Add(Parameters.OAuth_Nonce, value);
                else
                    this.RequestParameters[Parameters.OAuth_Nonce] = value;
            }
        }

        private string _version = string.Empty;
        public string Version
        {
            get { return _version; }
            set
            {
                _version = value;

                if (null == this.RequestParameters[Parameters.OAuth_Version])
                    this.RequestParameters.Add(Parameters.OAuth_Version, value);
                else
                    this.RequestParameters[Parameters.OAuth_Version] = value;
            }
        }

        private string _timestamp = string.Empty;
        public string Timestamp
        {
            get { return _timestamp; }
            set
            {
                _timestamp = value;

                if (null == this.RequestParameters[Parameters.OAuth_Timestamp])
                    this.RequestParameters.Add(Parameters.OAuth_Timestamp, value);
                else
                    this.RequestParameters[Parameters.OAuth_Timestamp] = value;
            }
        }

        #endregion


        #region CTor

        public OAuthDigitalSigner()
        {
            this.RequestParameters = new NameValueCollection();
            this.RequestParameters.Add(Parameters.Realm, "http://api.myspace.com/authentication");

        }

        #endregion

        #region Public Methods

        public void AddParameterSet(NameValueCollection parameterSet)
        {
            string[] keys = parameterSet.AllKeys;
            Array.Sort(keys);
            StringBuilder sb = new StringBuilder();

            foreach (string key in keys)
            {
                String[] valuesArray = parameterSet.GetValues(key);
                Array.Sort(valuesArray);
                foreach (string myvalue in valuesArray)
                {
                    this.RequestParameters.Add(key,
                   HttpUtility.UrlDecode(myvalue));
                }

            }
        }

        public string GetOAuthQueryString()
        {

            StringBuilder queryParams = new StringBuilder();
            queryParams.Append(Parameters.OAuth_Consumer_Key).Append('=').Append(this.RequestParameters[Parameters.OAuth_Consumer_Key]);
            queryParams.Append('&').Append(Parameters.OAuth_Token).Append('=').Append(this.RequestParameters[Parameters.OAuth_Token]);
            queryParams.Append('&').Append(Parameters.OAuth_Signature_Method).Append('=').Append(this.RequestParameters[Parameters.OAuth_Signature_Method]);
            queryParams.Append('&').Append(Parameters.OAuth_Signature).Append('=').Append(this.RequestParameters[Parameters.OAuth_Signature]);
            queryParams.Append('&').Append(Parameters.OAuth_Timestamp).Append('=').Append(this.RequestParameters[Parameters.OAuth_Timestamp]);
            queryParams.Append('&').Append(Parameters.OAuth_Nonce).Append('=').Append(this.RequestParameters[Parameters.OAuth_Nonce]);
            queryParams.Append('&').Append(Parameters.OAuth_Version).Append('=').Append(this.RequestParameters[Parameters.OAuth_Version]);
            return queryParams.ToString();
        }

        #endregion

        #region Private Methods

        public string CreateDigitalSignature()
        {
            this.DigitalSignature = GetDigitalSignature();
            return this.DigitalSignature;
        }

        private string GetDigitalSignature()
        {
            HashAlgorithm hashAlgorithm = null;

            string key = string.Format("{0}&{1}", ParameterEncode(this.ConsumerSecret), ParameterEncode(this.TokenSecret));

            switch (this.SigningMethod)
            {
                case SignatureMethod.PLAINTEXT:
                    return ParameterEncode(key);
                case SignatureMethod.HMAC_SHA1:
                    hashAlgorithm = new HMACSHA1(Encoding.UTF8.GetBytes(key));
                    break;
                case SignatureMethod.RSA_SHA1: //TODO: need to write support for RSA-SHA1
                    throw new NotImplementedException(GetSignatureMethodName(this.SigningMethod) + " signing algorithm not implemented.");
                default:
                    throw new NotImplementedException(GetSignatureMethodName(this.SigningMethod) + " signing algorithm not implemented.");

            }

            return Convert.ToBase64String(hashAlgorithm.ComputeHash(Encoding.UTF8.GetBytes(GetStringToSign())));
        }

        private string GetNormalizedParameterString()
        {
            //verify presence of parameters
            if (null == this.RequestParameters || this.RequestParameters.Count == 0)
                return string.Empty;


            NameValueCollection parameterKeys = new NameValueCollection(this.RequestParameters);
            parameterKeys.Remove(Parameters.Realm);
            parameterKeys.Remove(Parameters.OAuth_Signature);

            return GetSortedString(parameterKeys);

        }

        /// <summary>
        /// Gets the Sorted string to be signed
        /// </summary>
        /// <param name="list"></param>
        /// <returns></returns>
        private string GetSortedString(NameValueCollection list)
        {
            string[] keys = list.AllKeys;
            Array.Sort(keys);
            StringBuilder sb = new StringBuilder();

            foreach (string key in keys)
            {
                String[] valuesArray = list.GetValues(key);
                Array.Sort(valuesArray);
                foreach (string myvalue in valuesArray)
                {
                    sb.Append(key).Append('=');
                    sb.Append(ParameterEncode(myvalue));
                    sb.Append('&');
                }

            }
            if (sb.Length > 1)
                sb.Remove(sb.Length - 1, 1);

            return sb.ToString();

        }

        private string GetStringToSign()
        {
            if (null == this.RequestMethod || this.RequestMethod.Length == 0)
                throw new ArgumentException("No HttpMethod value supplied.", "HttpMethod");

            if (null == this.RequestUrl || this.RequestUrl.Length == 0)
                throw new ArgumentException("No Url value supplied.", "Url");

            StringBuilder stringToSign = new StringBuilder(this.RequestMethod.ToUpper().Trim()).Append('&');
            stringToSign.Append(ParameterEncode(this.RequestUrl.Trim())).Append('&');
            stringToSign.Append(ParameterEncode(GetNormalizedParameterString().Trim()));

            return stringToSign.ToString();
        }

        private string GetSignatureMethodName(SignatureMethod signatureMethod)
        {
            switch (signatureMethod)
            {
                case SignatureMethod.HMAC_SHA1:
                    return "HMAC-SHA1";
                case SignatureMethod.RSA_SHA1:
                    return "RSA-SHA1";
                default:
                    return string.Empty;
            }
        }

        private string ParameterEncode(string str)
        {
            string urlEncodedString = HttpUtility.UrlEncode(str);
            StringBuilder s = new StringBuilder(urlEncodedString);
            if (string.IsNullOrEmpty(urlEncodedString) || !urlEncodedString.Contains("%"))
                return s.ToString();
            for (int i = 0; i < s.Length; i++)
            {
                if (s[i] == '%')
                {
                    if (Char.IsDigit(s[i + 1]) && Char.IsLetter(s[i + 2]))
                    {
                        s[i + 2] = Char.ToUpper(s[i + 2]);
                    }
                }
            }
            return s.ToString();
        }
        #endregion



    }
}
